/*
 * JustEvasion, a simple game of fast reactions in OpenGL using freeglut.
    Copyright (C) 2011  James Heslin (PROGRAM_IX)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    James Heslin: program.ix.j@gmail.com
*/

#include "just-evasion.h"






void resetGame()
{
    star = PlayerStar();
    comboNum = 1;
    score = 0;
    lastCombo = 0;
    comboFade = 0;
    comboDisplay = false;
    killed = 0;
    isComboHit = false;
    dotSpawn  = -1;
    dots.clear();
    difficulty = 0;
    glClearColor (0.0, 0.0, 0.0, 1.0); // Black background
    star.x = (GLint)winWidth/2;
    star.y = (GLint)winHeight/2;
    cout << hello << endl;
    scoreStream.clear();
    scoreStream.str("");
    scoreStream << "Score: " << score << "\0";
    scoreString = scoreStream.str();
    displayScore();
    for(int i = 0; i < 8; i++)
    { 
        //cout << "Adding..." << i << endl;
        addNewDot();
    }
    //keys = new bool[256];
    //spKeys = new bool[256];
    paused = true;
    glutPostRedisplay();
}

//Draws a pixel on the screen at a given point. The CirclePt is legacy, 
// in case I decide to do the searchlight* thing again. I probably won't.
// * see comments in the method

void setPixel (GLint x, GLint y, CirclePt midpoint) // Set midpoint to someX, someY if you want searchlight cone type things    
{                                                   // But you'll have to define it above and take it out of this function signature
    /*glBegin (GL_LINES);
        glVertex2i (x, y);
        //glVertex2i (0,0); // THIS is the line that will make the searchlights happen. Comment the one below and uncomment this one.
        glVertex2i (midpoint.x, midpoint.y); // Alternate: makes circles composed of lines from the centre.
    glEnd ( );*/
    glBegin(GL_POINTS);
        glVertex2i(x,y);
    glEnd();
}

// Plots eight pixels in the circle

void circlePlotPoints (CirclePt circCtr, CirclePt circPt)
{
    setPixel (circCtr.x + circPt.x, circCtr.y + circPt.y, circCtr); // If you want the searchlight cones, remove the CirclePt from the    
    setPixel (circCtr.x - circPt.x, circCtr.y + circPt.y, circCtr); // setPixel header (and from these calls to it)
    setPixel (circCtr.x + circPt.x, circCtr.y - circPt.y, circCtr); 
    setPixel (circCtr.x - circPt.x, circCtr.y - circPt.y, circCtr);
    setPixel (circCtr.x + circPt.y, circCtr.y + circPt.x, circCtr);
    setPixel (circCtr.x - circPt.y, circCtr.y + circPt.x, circCtr);
    setPixel (circCtr.x + circPt.y, circCtr.y - circPt.x, circCtr);
    setPixel (circCtr.x - circPt.y, circCtr.y - circPt.x, circCtr);
}

void circleMidpoint (CirclePt circCtr, GLint radius)
{
    CirclePt circPt;
    GLint p = 1 - radius;         // Initial value of midpoint parameter.

    circPt.x = 0;             
    circPt.y = radius;

    void circlePlotPoints (CirclePt, CirclePt);

    /* Plot the initial point in each circle quadrant. */
    circlePlotPoints (circCtr, circPt);

    /* Calculate next points and plot in each octant. */
    while (circPt.x < circPt.y) 
    {
        circPt.x++;
        if (p < 0)
            p += 2 * circPt.x + 1;
        else 
        {
            circPt.y--;
            p += 2 * (circPt.x - circPt.y) + 1;
        }
        circlePlotPoints (circCtr, circPt);
    }
}

void init()
{
    glClearColor (0.0, 0.0, 0.0, 1.0); // Black background
    star.x = (GLint)winWidth/2;
    star.y = (GLint)winHeight/2;
    cout << hello << endl;
    for(int i = 0; i < 8; i++)
    {
        //cout << "Adding..." << i << endl;
        addNewDot();
    }
    glMatrixMode (GL_PROJECTION);
    gluOrtho2D (0.0, (GLdouble)winWidth, 0.0, (GLdouble)winHeight);
}

void drawStar()
{
    glColor3f (1.0, 1.0, 1.0);
    for(int i = 0; i < 10; i+=1)
    {
        glBegin(GL_POINTS);
            glVertex2i(star.x-i, star.y-i);
            glVertex2i(star.x+i, star.y+i);
            glVertex2i(star.x+i, star.y-i);
            glVertex2i(star.x-i, star.y+i);
            glVertex2i(star.x, star.y+i);
            glVertex2i(star.x, star.y-i);
            glVertex2i(star.x+i, star.y);
            glVertex2i(star.x-i, star.y);
        glEnd();
    }
}

void displayFcn()
{
    glClear (GL_COLOR_BUFFER_BIT);   //  Clear display window.
    drawStar();
    drawDots();
    displayScore();
    displayCombo();
    if(paused)
    {
        displayText("Press P to continue.", winWidth/2-100, 5);
    }
    else
    {
        showFPS();
    }
    glutSwapBuffers ();
}

void winReshapeFcn (int newWidth, int newHeight)
{
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ( );
    gluOrtho2D (0.0, GLdouble (newWidth), 0.0, GLdouble (newWidth));

    /*  Reset display-window size parameters.  */
    winWidth = newWidth;
    winHeight = newHeight;    
    star.x = winWidth/2;
    star.y = winHeight/2;
    glutPostRedisplay();
}

void moveStar()
{
    // Combined control method - WASD and arrow keys       
    cout << keys['d'] << " " << keys['a'] << " " << keys['s'] << " " << keys['w'] << endl;
    if((keys['d'] || spKeys[GLUT_KEY_RIGHT]) && star.x < winWidth)
    {
        //cout << "d";
        star.x+=star.speed;
    }
    else if((keys['a'] || spKeys[GLUT_KEY_LEFT]) && star.x > 0)
    {
        //cout << "a";
        star.x-=star.speed;
    }    
        
    if((keys['s'] || spKeys[GLUT_KEY_DOWN]) && star.y > 0)
    {
        //cout << "s";
        star.y-=star.speed;
    }    
        
    else if((keys['w'] || spKeys[GLUT_KEY_UP]) && star.y < winHeight)
    {
        //cout << "w";
        star.y+=star.speed;
    }
    
    
     //arenaWrap();
}

void arenaWrap()
{
    // \/ This makes it a bit too easy
    if(star.x >= winWidth) // This code makes the arena wrap around 
    {
        star.x = 10;
        //cout << "R" << endl;
    }
    else if(star.x <= 0) // Going off one side means you appear at opposite
    {
        star.x = winWidth-10;
        //cout << "L" << endl;
    }
    if(star.y >= winHeight) // Only works for the star though
    {
        star.y = 10;
        //cout << "T" << endl;
    }
    else if(star.y <= 0)
    {
        star.y = winHeight-10;
        //cout << "B" << endl;
    }
}

bool starInCircle(int i)
{
    return (star.x - dots.at(i).x < level && star.x - dots.at(i).x > -level) && (star.y - dots.at(i).y < level && star.y - dots.at(i).y > -level);
}

void endGameTest(int i)
{
    if(starInCircle(i))
    { //Is the star within the circle?
        cout << "GAME OVER" << endl;
        cout << scoreString << endl;
        cout << "You forced " << killed << " overlaps. Well done!" << endl;
        resetGame();
        //exit(0);
    }
}
void setTextColour()
{
    glColor3f(1.0f, 1.0f, 1.0f);
}

void displayScore()
{
    setTextColour();
    glRasterPos2i(5, 5);
    
    //glutBitmapString(GLUT_BITMAP_HELVETICA_18, (const unsigned char*) scoreString.c_str());
    glutBitmapString(GLUT_BITMAP_9_BY_15, (const unsigned char*) scoreString.c_str());
    //glutStrokeString(GLUT_STROKE_MONO_ROMAN, (const unsigned char*) scoreString.c_str());

}

void displayText(string text, int rX, int rY)
{
    setTextColour();
    glRasterPos2i(rX, rY);
    
    //glutBitmapString(GLUT_BITMAP_HELVETICA_18, (const unsigned char*) scoreString.c_str());
    glutBitmapString(GLUT_BITMAP_9_BY_15, (const unsigned char*) text.c_str());
    //glutStrokeString(GLUT_STROKE_MONO_ROMAN, (const unsigned char*) scoreString.c_str());

}

void incrementScore()
{
    score+=100;
    if(isComboHit) 
    {
        score+=(50 * comboNum);
        cout << "COMBO! x" << comboNum << endl;
        lastCombo = comboNum;
        comboDisplay = true;
        comboFade = 0;
    }
    scoreStream.clear();
    scoreStream.str("");
    scoreStream << "Score: " << score << "\0";
    scoreString = scoreStream.str();
}

void setCombo()
{
    isComboHit = true;
    comboNum++;
}

void unsetCombo()
{
    isComboHit = false;
    comboNum = 1;
}

void displayCombo()
{
    if(comboDisplay)
    {
        setTextColour();
        glRasterPos2i(5, 20);
        //cout << "Displaying COMBO! x" << lastCombo << endl;
        scoreStream.clear();
        scoreStream.str("");
        scoreStream << "COMBO! x" << lastCombo << "\0";
        comboString = scoreStream.str();
        glutBitmapString(GLUT_BITMAP_9_BY_15, (const unsigned char*) comboString.c_str());
        if(comboFade >= 60)
        {
            comboDisplay = false;
            comboFade = 0;
        }
        comboFade++;
    }
}

void dotOverlap(int i)
{
    for(int j = 0; j < (int)dots.size(); j++)
    {
        if(j != i)
        {
            //if(dots.at(i).x == dots.at(j).x && dots.at(i).y == dots.at(j).y)
            if((dots.at(i).x - dots.at(j).x < 2 && dots.at(i).x - dots.at(j).x > -2) && (dots.at(i).y - dots.at(j).y < 2 && dots.at(i).y - dots.at(j).y > -2))
            {
                killed++;
                addNewDot(j);
                incrementScore();
                cout << "Hit!" << endl;
                setCombo();
            }
        }
    }
}   

void drawDots()
{
    
    for(int i = 0; i < (int)dots.size(); i++)
    {
        glColor3f (dots.at(i).r, dots.at(i).g, dots.at(i).b);  
        //if(i == 0)
        //cout << dots.at(i).x << " " << dots.at(i).y << endl;
        circleMidpoint(dots.at(i), 15);
    }
}

void manageDots()
{
    for(int i = 0; i < (int)dots.size(); i++)
    {
        if(star.x < dots.at(i).x) // Follow the star 
        {
            dots.at(i).x-=dots.at(i).speed;
        }
        else if(star.x > dots.at(i).x)
        {
            dots.at(i).x+=dots.at(i).speed;
        }
        
        if(star.y < dots.at(i).y)
        {
            dots.at(i).y-=dots.at(i).speed;
        }
        else if(star.y > dots.at(i).y)
        {
            dots.at(i).y+=dots.at(i).speed;
        }
        
        dotOverlap(i);
        
        endGameTest(i);
    }
    unsetCombo();
}

CirclePt makeNewDot()
{
    int newSpeed;
    if(dots.size() == 0)
    {
        newSpeed = 1;
    }
    else
    {
        newSpeed = dots.at(0).speed;
    }
    dotSpawn++;
    // << dotSpawn << endl;
    if(dotSpawn % 9 == 0)
    {
        //cout << "0" << endl;
        return (CirclePt(0, 0, newSpeed, 0.5f, 1.0f, 0.7f)); // Bottom left
    }
    if(dotSpawn % 9 == 1)
    {
        //cout << "1" << endl;
        return (CirclePt(0, winHeight, newSpeed, 1.0f, 1.0f, 0.7f)); // Top left
    }
    if(dotSpawn % 9 == 2)
    {
        //cout << "2" << endl;
        return (CirclePt(winWidth, 0, newSpeed, 0.5f, 0.5f, 0.2f)); // Bottom right
    }
    if(dotSpawn % 9 == 3)
    {
        //cout << "3" << endl;
        return (CirclePt(winWidth, winHeight, newSpeed, 0.5f, 0.2f, 0.8f)); // Top right
    }
    if(dotSpawn % 9 == 4)
    {
        //cout << "4" << endl;
        return (CirclePt(winWidth/2, 0, newSpeed, 0.1f, 1.0f, 0.5f)); // Bottom centre edge
    }
    if(dotSpawn % 9 == 5)
    {
        //cout << "5" << endl;
        return (CirclePt(0, winHeight/2, newSpeed, 0.5f, 0.5f, 0.5f)); // Left centre edge
    }
    if(dotSpawn % 9 == 6)
    {
        //cout << "6" << endl;
        return (CirclePt(winWidth/2, winHeight, newSpeed, 0.3f, 0.8f, 0.7f)); // Top centre edge
    }
    if(dotSpawn % 9 == 7)
    {
        //cout << "7" << endl;
        return (CirclePt(winWidth, winHeight/2, newSpeed, 0.1f, 1.0f, 1.0f)); // Right centre edge
    }
    
    if(dotSpawn % 9 == 8)
    {
        //cout << "8" << endl;
        return (CirclePt(winWidth/2, winHeight/2, newSpeed, 0.2f, 0.5f, 1.0f)); // Centre
    }
    return(CirclePt(0, 0, newSpeed, 1.0f, 1.0f, 1.0f));
    
}

void addNewDot(int i)
{
    dots.at(i) = makeNewDot();
}

void addNewDot()
{
    dots.push_back(makeNewDot());
}


// Increments the difficulty counter
// At intervals, adds new circles
// Maintains the difficulty counter at a reasonable size
void difficultyMod()
{
    difficulty++;
    if(difficulty % 500 == 0)
    {
        cout << "New enemy!" << endl;
        addNewDot();
    }
    if(difficulty == 32001)
    {
        //cout << "Watch out: SPEED INCREASE!" << endl;
        //for(int i = 0; i < dots.size(); i++)
        //{
        //    dots.at(i).speed++;
        //}
        difficulty = 1;
    }
}

//This is broadly equivalent to the update() method in ActionScript/XNA
void posChange()
{ 
    if(!paused) // This is the only test that manages pausing - it's the only one necessary.
    {
        moveStar();

        manageDots();
        //drawDots(); // CHECK THAT YOU'RE NOT DRAWING SOMETHING TWICE, DOUCHE
        
        difficultyMod();
        glutPostRedisplay();
    }    
}

void pauseUnpause()
{
    if(paused)
    {
        cout << "Unpausing" << endl;
        paused = false;
    }
    else
    {
        cout << "Pausing" << endl;
        paused = true;
    }
    //spKeys = new bool[256];
    //keys = new bool[256];
}

void showFPS()
{
    //calculate the frames per second
	frame++;

	//get the current time
	currentTime = glutGet(GLUT_ELAPSED_TIME);
	
	//check if a second has passed
	if (currentTime - timeBase > 1000) 
	{
		cout << (frame*1000.0/(currentTime-timeBase)) ;
	 	timeBase = currentTime;		
		frame = 0;
	}
}

void keyDown(unsigned char ch, int x, int y)
{
    if (ch == 27)
    {
        glutSetKeyRepeat(GLUT_KEY_REPEAT_DEFAULT);
        exit(0);
    }
    
    keys[ch] = true;    
}

void keyUp(unsigned char ch, int x, int y)
{
    if(ch == 'p')
    {
        pauseUnpause();
    }
    keys[ch] = false;
}

void spKeyDown(int key, int x, int y)
{
    spKeys[key] = true;       
}

void spKeyUp(int key, int x, int y)
{
    spKeys[key] = false;
}

void mouseButton(int button, int state, int x, int y)
{
    //star.x= x;
    //star.y= y;
    //glutPostRedisplay();
}
 
void mouseMove(int x, int y)
{
    //star.x=x;
    //star.y=y;
    //glutPostRedisplay();
}

int main(int argc, char** argv)
{
    glutInit (&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition (100, 100);
    glutInitWindowSize (winWidth, winHeight);
    glutCreateWindow("Just Evasion");
    glutSpecialFunc(spKeyDown);
    glutSpecialUpFunc(spKeyUp);
    glutKeyboardFunc(keyDown);
    glutKeyboardUpFunc(keyUp);
    glutMouseFunc(mouseButton);
    glutMotionFunc(mouseMove);
    glutIdleFunc(posChange);
    init();
    glutDisplayFunc (displayFcn);
    glutReshapeFunc (winReshapeFcn);
    glutMainLoop();
}
